package worker;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.objectweb.proactive.ActiveObjectCreationException;
import org.objectweb.proactive.api.PAActiveObject;
import org.objectweb.proactive.core.body.migration.MigrationException;
import org.objectweb.proactive.core.node.NodeException;

public class ManagerImpl implements Serializable, Manager {

	/**
	 * 
	 */
	private static final long serialVersionUID = 8133689553324951699L;

	protected long heapSize;
	protected Master master;
	protected Worker worker;
	protected ArrayList<Backup> backups = new ArrayList<Backup>();
	protected Backup backup;	// Uniquement pour la version de test !
	public String nodeUrl;		// Utile pour contourner le bug



	public ManagerImpl () { 
		heapSize = Runtime.getRuntime().totalMemory();
	}

	public long getHeapSize() { return heapSize; }
	public Worker getWorker() { return worker; }
	public Backup getBackup() { return backup; }
	public String getNodeUrl() { return nodeUrl; }
	public ManagerImpl getStub () { return (ManagerImpl) PAActiveObject.getStubOnThis(); }

	
	/**
	 * Accesseur wrappant le master pour restauration éventuelle du systeme
	 * @throws MigrationException 
	 * @throws NodeException 
	 * @throws ActiveObjectCreationException 
	 */
	public Master getMaster() throws ActiveObjectCreationException, NodeException, MigrationException { 
		if (master == null) return null;
		if (PAActiveObject.getActiveObjectNodeUrl(master).equals(PAActiveObject.getActiveObjectNodeUrl(PAActiveObject.getStubOnThis()))) return master;
		if (! PAActiveObject.pingActiveObject(master)) restoreMaster();
		return master; 
	}
	

	// Initialise le 1er manager : cree le master
	public void init () throws ActiveObjectCreationException, NodeException, MigrationException  {
		nodeUrl = PAActiveObject.getActiveObjectNodeUrl(PAActiveObject.getStubOnThis());
		master  = PAActiveObject.newActive(Master.class, null);
		worker  = PAActiveObject.newActive(Worker.class, null);
		//backup  = PAActiveObject.newActive(Backup.class, null); 

		add2Master(this);
	}

	// Initialise le manager : recupere le master
	public void connectTo (ManagerImpl ref) throws ActiveObjectCreationException, IOException, NodeException, MigrationException {
				
		nodeUrl = PAActiveObject.getActiveObjectNodeUrl(PAActiveObject.getStubOnThis());
		master  = ref.getMaster();
		//backup  = ref.getBackup();
		worker  = PAActiveObject.newActive(Worker.class, null);

		add2Master(this);
	}


	// Ajoute un worker et fait migrer le master sur le dernier ajouté et le backup sur l'avant-dernier
	public void add2Master (ManagerImpl m) throws ActiveObjectCreationException, NodeException, MigrationException {
		
		/*
		 * ---- Version de test, à supprimer lorsque le bug sera corrigé... -----
		 */
		
		master.add(m.getStub());

		ArrayList<Maillon> maillons = master.getList();

		if (maillons.size() <= 1) {
			//master.moveTo (maillons.get(0).getUrl()); // --> on contourne le pb en memorisant l'url plus haut...
			master.moveTo (PAActiveObject.getActiveObjectNodeUrl(maillons.get(0).getManager()));
		}
		else {  
			//master.moveTo (maillons.get(maillons.size() - 1).getUrl());
			master.moveTo (PAActiveObject.getActiveObjectNodeUrl(maillons.get(maillons.size() - 1).getManager()));
		}

		
		
		/*
		 * ---- Version fonctionnelle, à utiliser lorsque le bug sera corrigé... -----
		 */
		
		/* * /
		// On ajoute le nouveau manager au master
		int index = this.getMaster().add(m);

		// Si le nouveau worker devient this.getMaster()
		if (index == 0 && this.getMaster().getMaillons().size() > 1) {
			this.getMaster().moveTo(PAActiveObject.getUrl(m));
			
			updateMaster(); // On met à jour la référence du this.getMaster() chez tous les managers (évite les forwarders)
		}
		
		int nbBackup = this.getMaster().getNbBackup();
		
		// si on n'a pas besoin de créer un nouveau backup 
		if (this.getMaster().getMaillons().size() == 1 || index > nbBackup || nbBackup == 0) { return; }

		// on crée le nouveau backup (à la bonne place)
		addBackup(index - 1, this.getMaster());
		
		// si on avait déjà le bon nombre de backups, on supprime le backup de trop (le dernier de la liste)
		if (backups.size() > nbBackup)
			PAActiveObject.terminateActiveObject(backups.get(backups.size() - 1), true);

		// on met à jour la liste des backups dans tous les managers
		updateBackup();
		
		/* */
	}

		
	@Override
	public void restoreMaster() throws ActiveObjectCreationException, NodeException, MigrationException {
		Backup bkp = null;

		// Etat des lieux des Backups
		for (int i = 0 ; i < backups.size() && bkp == null ; ++i)
			if (PAActiveObject.pingActiveObject(backups.get(i))) bkp = backups.get(i);

		if (bkp == null) {
			// TOUS morts !
			System.err.println("Impossible de restaurer le système");
			System.exit(0);
		}

		Master newMaster = bkp.restore();

		// Etat des lieux des Workers
		List<Integer> workersDown = new ArrayList<Integer>();
		for (int i = 0 ; i < newMaster.getMaillons().size() ; ++i)
			if (! PAActiveObject.pingActiveObject(newMaster.getMaillons().get(i).getManager()))
				workersDown.add(i);

		// Mise a jour de la liste des workers et backups
		for (int i = workersDown.size() ; i-- != 0 ; ) {
			newMaster.getMaillons().remove(workersDown.get(i));
			backups.remove(workersDown.get(i) - 1);
		}

		// Mise a jour des backups encore vivants
		for (int i = 0 ; i < backups.size() ; ++i)
			backups.get(i).updateFromMaster(newMaster);

		// On recrée les backups manquants
		for (int i = backups.size() ; backups.size() < newMaster.getNbBackup() && i + 1 < newMaster.getMaillons().size() ; ++i) {
			addBackup (newMaster);
		}

		// On renvoie le master sur la 1ere machine
		master = PAActiveObject.turnActive(newMaster, PAActiveObject.getActiveObjectNode(newMaster.getMaillons().get(0).getManager()));

		// Update du master et des workers chez tous les workers (Manager)
		updateMaster();
		updateBackup();
		
	}

	@Override
	public void createMaster(int nbBackup) {
		try {
			this.master = PAActiveObject.newActive(Master.class, new Object[] {nbBackup});
		} catch (Exception e) {
			e.printStackTrace();
		}		
	}

	@Override
	public void addBackup(Master m) throws ActiveObjectCreationException, NodeException, MigrationException {
		addBackup(backups.size(), m);		
	}

	@Override
	public void addBackup(int index, Master m) throws ActiveObjectCreationException, NodeException,	MigrationException {
		backups.add(index, PAActiveObject.newActive(Backup.class, new Object[] {m}, PAActiveObject.getActiveObjectNode(m.getMaillons().get(index + 1).getManager())));		
	}


	@Override
	public void updateMaster() throws ActiveObjectCreationException, NodeException, MigrationException {
		for (Maillon m : this.getMaster().getMaillons())
			m.getManager().setMaster(master);
	}

	@Override
	public void updateBackup() throws ActiveObjectCreationException, NodeException, MigrationException {
		for (Maillon m : this.getMaster().getMaillons())
			m.getManager().setBackups(backups);
	}

	@Override
	public int getCurrentNbBackup() {
		return backups.size();
	}

	@Override
	public void setNbBackup(int nbBackup) throws ActiveObjectCreationException, NodeException, MigrationException {
		this.getMaster().setNbBackup(nbBackup);
	}

	@Override
	public List<Worker> getWorkers() throws ActiveObjectCreationException, NodeException, MigrationException {
		return this.getMaster().getWorkers();
	}

	@Override
	public void freeWorkers(ArrayList<Worker> workers) throws ActiveObjectCreationException, NodeException, MigrationException {
		this.getMaster().freeWorkers(workers);		
	}

	@Override
	public void setMaster(Master m) {
		this.master = m;
	}

	@Override
	public void setBackups(ArrayList<Backup> list) {
		this.backups = list;
	}


}
